############################################################################
#
# DIM - A Direct Interaction Manager for SAGE
# Copyright (C) 2007 Electronic Visualization Laboratory,
# University of Illinois at Chicago
#
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above
#    copyright notice, this list of conditions and the following disclaimer
#    in the documentation and/or other materials provided with the distribution.
#  * Neither the name of the University of Illinois at Chicago nor
#    the names of its contributors may be used to endorse or promote
#    products derived from this software without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Direct questions, comments etc about SAGE UI to www.evl.uic.edu/cavern/forum
#
# Author: Ratko Jagodic
#        
############################################################################




########################################################################
#
# Creates some global widgets for the whole display
#
# Author: Ratko Jagodic
#
########################################################################

from globals import *
import overlays.menu as menu
import overlays.button as button
import overlays.icon as icon
import overlays.thumbnail as thumbnail
import overlays.label as label
import overlays.sizer as sizer
import overlays.panel as panel
import overlays.app as appOverlayObj
import overlays.multiSelect as multiSelect
import time, copy, os, os.path
from threading import Thread
import traceback as tb
from math import ceil, sqrt, floor
from sageGate import addGate, getGate

import pprint


def makeNew():
    """ this is how we instantiate the object from
    the main program that loads this plugin """
    return OrganizationUI()


# must have this to load the plugin automatically
TYPE = UI_PLUGIN


# tile modes
FREE_MODE = 0
TILE_MODE = 1
ONE_PER_DISPLAY_MODE = 2

# To know the each button's state
# when sharing through 'ALL' button
# see _onShare function
ShareWithButtons = []

# Sungwon multi-site sharing
class ShareButton(button.Button):
    def __init__(self, shareWithHost, app=None):
        button.Button.__init__(self, app)

        # Put a special icon
        self.setUpImage( "images/share_button_up.png" )
        self.setDownImage( "images/default_button_up.png" )

        # The button has a member variable for the IP address
        self.__shareWithHost = shareWithHost
        self.__callback = None

        # toggle to enable/disable sharing
        # when a media is dropped on to the 'ALL' button
        # Off to start with
        self.setToggle(True)
        self.setState(BUTTON_DOWN)

        # click on sets the host as slave for its master
        self.onUp(self.onMasterUp)
        self.onDown(self.onMasterDown)

    def onMasterUp(self, btn):
        setMasterSite(self.__shareWithHost)
        #print("onMaster", getMasterSite(), getMasterSiteMode() )

    def onMasterDown(self, btn):
        setMasterSite(None)
        #print("onMaster", getMasterSite(), getMasterSiteMode() )


    def onDrop(self, callback):
        self.__callback = callback
        button.Button.onDrop(self, self._internalOnDropCallback)

    def _internalOnDropCallback(self, event):
        # write the data here
        # DropEvent in events.py has the data field
        event.data = self.__shareWithHost
        #if self.getState() == BUTTON_UP:
        self.__callback(event)


class OrganizationUI:
    def __init__(self):
        self.addWidget = getOverlayMgr().addWidget
        self.sageGate = getSageGate()
        self.sageData = getSageData()
        self.evtMgr = getEvtMgr()
        self.overlayMgr = getOverlayMgr()

        self.fileServer = getFileServer()
        
        # display stuff
        self.disp = self.sageData.getDisplayInfo()
        self.sageW, self.sageH = self.disp.sageW, self.disp.sageH

        # event stuff
        self.evtMgr.register(EVT_NEW_APP, self.__onNewApp)
        self.evtMgr.register(EVT_APP_KILLED, self.__onAppKilled)
        #self.evtMgr.register(EVT_APP_INFO, self.__onAppInfo)

        # adjust the usable height to NOT include the bottom minimize bar
        self.disp.usableH -= int(self.sageH * MINIMIZE_BAR_SIZE)
        self.disp.usableZeroY = int(self.sageH * MINIMIZE_BAR_SIZE)

        # tile stuff
        self.__prevButton = None
        self.__tileMode = FREE_MODE
        #self.__tiledWindows = []

        if SHOW_USER_INTERFACE:
            # the UI is created when the display info comes in... _onDisplayInfo
            self.__makeUI()

        # so that others can use this object to initiate organization
        setLayoutOrganization(self)


    def __makeUI(self):
        self.__fs = NORMAL
        
        p = panel.makeNew()
        p.align(CENTER, TOP)
        p.fitInWidth(False)
        p.fitInHeight(False)
        p.setSize(1.0, 40)
        p.setBackgroundColor(0,0,0)
        p.setTransparency(150)
        p.z = TOP_Z

        # adjust the usable height to NOT include the top UI bar
        hh = p.getFinalHeight()
        self.disp.usableH -= hh
        
        #topSizer = sizer.makeNew()
        #topSizer.align(CENTER, TOP, yMargin=-1)
        #topSizer.align(CENTER, TOP, yMargin=4)
        #topSizer.addChild(self.__makeActionMenu())
        #topSizer.addChild(self.__makeLayoutButtons())

        #p.addChild(topSizer)
        self.addWidget(p)

        
    def makeLayoutButtons(self, bs=30, alpha=255):
        btnWidth = int(bs*3) 

        gb = thumbnail.makeNew()
        gb.align(CENTER, CENTER, xMargin=20, yMargin=10)
        #gb.setToggle(True)
        gb.setLabel("Tile", fontSize=NORMAL)
        gb.alignLabel(CENTER)
        gb.setSize(btnWidth, bs)
        gb.onDown(self.__onLayoutBtn)
        gb.onUp(self.__onLayoutBtn)
        gb.setTransparency(alpha)
        return gb

        """
        s = sizer.makeNew()
        s.align(CENTER, TOP)

        l = label.makeNew(label="Layout Mode:")
        l.align(CENTER, CENTER, xMargin=100)
        l.drawBackground(False)
        l.setTransparency(150)
        s.addChild(l)
        
        gb = button.makeNew()
        gb.setRadio()
        #gb.setToggle(True, True)
        gb.align(CENTER, CENTER, xMargin=5)
        gb.setLabel("Grid", fontSize=self.__fs)
        gb.alignLabel(CENTER)
        gb.setSize(btnWidth,30)
        gb.onDown(self.__onLayoutBtn)
        gb.setUserData(TILE_MODE)
        s.addChild(gb)

        ob = button.makeNew()
        ob.setRadio()
        #ob.setToggle(True, True)
        ob.setLabel("1 per Tile", fontSize=self.__fs)
        ob.alignLabel(CENTER)
        ob.setSize(btnWidth,30)
        ob.align(CENTER, CENTER, xMargin=5)
        ob.onDown(self.__onLayoutBtn)
        ob.setUserData(ONE_PER_DISPLAY_MODE)
        s.addChild(ob)

        self.fb = button.makeNew()
        #self.fb.setToggle(True, True)
        self.fb.setRadio()
        self.fb.setLabel("Free", fontSize=self.__fs)
        self.fb.alignLabel(CENTER)
        self.fb.setSize(btnWidth,30)
        self.fb.align(CENTER, CENTER, xMargin=5)
        self.fb.onDown(self.__onLayoutBtn)
        self.fb.setUserData(FREE_MODE)
        self.fb.setState(BUTTON_DOWN)
        s.addChild(self.fb)
    
        self.__prevButton = self.fb

        return s
        """
        

    def __makeActionMenu(self):
        s = sizer.Sizer()
        s.align(CENTER, TOP)

        l = label.makeNew(label="Drop windows to:")
        l.drawBackground(False)
        l.setTransparency(150)
        l.align(CENTER, CENTER, xMargin=50*getGlobalScale())
        s.addChild(l)
        
        if getSharedHost():
            """
            b = button.Button()
            b.setLabel("Share with: "+str(getSharedHostName()), fontSize=self.__fs)
            b.alignLabel(CENTER)
            b.setSize(300,40)
            b.align(CENTER, TOP)
            b.onDrop(self.__onShare)
            s.addChild(b)
            """
            # Sungwon multi-site sharing
            names = getSharedHostName()
            hosts = getSharedHost()
            if len(hosts) > 1:
                # attach 'all' button
                b = button.Button()
                # Put a special icon
                b.setUpImage( "images/shareall_button_up.png" )
                b.setLabel("All sites", fontSize=self.__fs)
                b.alignLabel(CENTER)
                b.setSize(200,40)
                b.align(CENTER, TOP, xMargin=30*getGlobalScale())
                b.onDrop(self.__onShare)
                b.onUp(self.__onSync)
                s.addChild(b)

            for i in range(len(hosts)): 
                # each shareWith button has ip address
                b = ShareButton(str(hosts[i]))
                b.setLabel(str(names[i]), fontSize=self.__fs)
                b.alignLabel(CENTER)
                b.setSize(200,40)
                b.align(CENTER, TOP, xMargin=30*getGlobalScale())
                b.onDrop(self.__onShare)
                s.addChild(b)
                ShareWithButtons.insert(i, b)

        b = button.Button()
        b.setLabel("Push to Back", fontSize=self.__fs)
        b.alignLabel(CENTER)
        b.setSize(350,40)
        if getSharedHost():
            b.align(CENTER, TOP, xMargin=70*getGlobalScale())
        else:
            b.align(CENTER, TOP, xMargin=40*getGlobalScale())
        b.onDrop(self.__onPushToBack)
        s.addChild(b)

        b = button.Button()
        b.setLabel("Remove", fontSize=self.__fs)
        b.alignLabel(CENTER)
        b.setSize(500,40)
        b.align(CENTER, TOP,xMargin=70*getGlobalScale())
        b.onDrop(self.__onRemoveBtn)
        s.addChild(b)

        return s
    

    #######-----------   EVENT HANDLING  -----------#######


    def __onRemoveBtn(self, event):
        def __removeThumb(thumb):
            self.overlayMgr.removeWidget(thumb)

            filePath, mediaType = thumb.getUserData()
            if mediaType == "saved":    # deleting states
                try:
                    statePath = os.path.splitext(filePath)[0]+".state"
                    os.remove(statePath) # the state
                    os.remove(filePath)  # the image (screenshot thumbnail)
                except:
                    tb.print_exc()
                   
            elif os.path.dirname(filePath).endswith("Trash"):  # deleting images
                self.fileServer.DeleteFilePermanently(filePath)
                
            else:
                self.fileServer.DeleteFile(filePath)
                    

            # app window dropped?
        if isinstance(event.object, appOverlayObj.App):
            self.sageGate.shutdownApp(event.object.app.getId())
            writeLog(LOG_WINDOW_REMOVE, event.object.app.getId())
            event.droppedOnTarget = True

            # thumbnail from mediaBrowser dropped?
        elif isinstance(event.object, thumbnail.Thumbnail):
            __removeThumb(event.object)
            event.droppedOnTarget = True

            # many objects dropped?
        elif isinstance(event.object, multiSelect.MultiSelect):
            selectedOverlayType = event.object.getSelectedOverlayType()
            selectedOverlays = event.object.getSelectedOverlays()

            # close apps
            if selectedOverlayType == OVERLAY_APP:
                for appObj in event.object.getSelectedApps():
                    self.sageGate.shutdownApp(appObj.getId())
                    writeLog(LOG_WINDOW_REMOVE, appobj.app.getId())

            # delete thumbnails
            elif selectedOverlayType == OVERLAY_THUMBNAIL:
                for obj in selectedOverlays:
                    __removeThumb(obj)

            event.droppedOnTarget = True
            event.object.deselectAllOverlays()            


    def __onMaximizeWindow(self, event):
        selectedOverlays = []
        
        if isinstance(event.object, appOverlayObj.App):
            doMax = not event.object.app.maximized

            event.object.app.toggleMaximized()
            event.droppedOnTarget = True
            selectedOverlays.append(event.object)

        elif isinstance(event.object, multiSelect.MultiSelect):
            selectedApps = event.object.getSelectedApps()
            selectedOverlays = event.object.getSelectedOverlays()
            
            doMax = not selectedApps[0].maximized
            if doMax:
                self.tileWindows(selectedApps)
            else:
                self.restoreFromMaximize(selectedApps)

            event.droppedOnTarget = True

        # dim or un-dim the other apps
        #for app in self.overlayMgr.getOverlaysOfType(OVERLAY_APP):
        #    if app not in selectedOverlays and not app.app.isMinimized():
        #        app.dim(doMax)
                
    def _onAppDoubleClick(self, event, appOverlay, twoDoubleClicks):
        apps = []  # SageApp objects
        app = appOverlay.app

        # on which apps are we operating?
        #if appOverlay.isSelected():
        #    apps = event.device.getMultiSelect().getSelectedApps()
        #else:
        #    apps.append(app)
        if twoDoubleClicks : 
            if not app.maximized:   # section maximize
                getLayoutSections().setMaximized(app)
                #app.saveBoundsBeforeMax()
                #app.focusWindow()
                #app.maximize()
                #app.sectionMaximized = False
                #app.maximized = True
                # adjust
                #getLayoutSections().adjustCenter(app)
                #writeLog(LOG_WINDOW_MAXIMIZE, app.getId(), app.getBounds())
        else : 
            if not app.sectionMaximized:   # section maximize
                app.saveBoundsBeforeMax()
                app.sec_maximize()
                app.sectionMaximized = True 
                app.maximized = False 
                writeLog(LOG_WINDOW_MAXIMIZE, app.getId(), app.getBounds())
            else : 
                apps = []
                apps.append(app)
                self.restoreFromMaximize(apps)
                del apps
        '''
        # figure out what to do
        if twoDoubleClicks and app.sectionMaximized and app.section != getLayoutSections().root:    # just a single double click
            #print "FULL maximize"
            
            self.tileWindows(apps, saveAllBounds=False)
            for a in apps:
                writeLog(LOG_WINDOW_MAXIMIZE, a.getId(), a.getBounds())
                a.sectionMaximized = False
                a.maximized = True
        else:
            if not app.maximized and not app.sectionMaximized:   # section maximize
                #print "section maximize"
                self.tileWindows(apps, sectionToTile=app.section)
                for a in apps:
                    writeLog(LOG_WINDOW_MAXIMIZE, a.getId(), a.getBounds())
                    a.sectionMaximized = True
                    a.maximized = False
            else:                      # restore
                #print "restoring SINGLE"
                self.restoreFromMaximize(apps)
                
        '''
            
        # dim or un-dim the other apps
        #for app in self.overlayMgr.getOverlaysOfType(OVERLAY_APP):
        #    if app not in selectedOverlays and not app.app.isMinimized():
        #        app.dim(doMax)



    def __onPushToBack(self, event):
        if isinstance(event.object, appOverlayObj.App):
            self.sageGate.pushToBack(event.object.app.getId())
            event.object._updateAppOverlays()
            event.object._resetOverlay() 
            event.droppedOnTarget = True

        elif isinstance(event.object, multiSelect.MultiSelect):
            selectedOverlays = event.object.getSelectedOverlays()
            for appOverlay in selectedOverlays:
                self.sageGate.pushToBack(appOverlay.app.getId())
                appOverlay._updateAppOverlays()
                appOverlay._resetOverlay()
            event.droppedOnTarget = True
            event.deselectAll = True


    def __onSync(self, event):
        local_apps = self.sageData.getRunningApps()
        for lapp in local_apps:
            if local_apps[lapp].isShared:
                # sync the remote copies
                self.overlayMgr.getAppOverlay(local_apps[lapp])._sendAllRemoteSync()
                # sync itself
                self.sageGate.sendAppEvent(APP_SYNC, local_apps[lapp].getSailID())

    def __onShare(self, event):
        if isinstance(event.object, appOverlayObj.App):
            # event.data is defiend in DropEvent in events.py
            # event.data is the ip address
            if event.data == None:
                # to ALL
                hosts = getSharedHost()
                buttons = ShareWithButtons
                for i in range(len(hosts)):
                    if buttons[i].getState() == BUTTON_UP:
                        addGate(hosts[i])
                        self.sageGate.showContentRemotely(event.object.app, hosts[i])
                        event.object.app.sharedHosts.add(hosts[i])
            else:
                addGate(event.data)
                self.sageGate.showContentRemotely(event.object.app, event.data)
                event.object.app.sharedHosts.add(event.data)
            # Enables the sync behavior across sites
            event.object.app.isShared = True
            event.object._updateAppOverlays()
            event.object._resetOverlay() 
            event.droppedOnTarget = True
            # send a SYNC message to the app after sharing
            self.sageGate.sendAppEvent(APP_SYNC, event.object.app.getSailID())

        
        elif isinstance(event.object, multiSelect.MultiSelect):
            selectedOverlays = event.object.getSelectedOverlays()
            for appOverlay in selectedOverlays:
                # send a SYNC message to the app befor sharing
                self.sageGate.sendAppEvent(APP_SYNC, appOverlay.app.getSailID())
                if event.data == None:
                    # to ALL
                    hosts = getSharedHost()
                    buttons = ShareWithButtons
                    for i in range(len(hosts)):
                        if buttons[i].getState() == BUTTON_UP:
                            addGate(hosts[i])
                            self.sageGate.showContentRemotely(appOverlay.app, hosts[i])
                            appOverlay.app.sharedHosts.add(hosts[i])
                else:
                    addGate(event.data)
                    self.sageGate.showContentRemotely(appOverlay.app, event.data)
                    appOverlay.app.sharedHosts.add(event.data)
                appOverlay.app.isShared = True
                appOverlay._updateAppOverlays()
                appOverlay._resetOverlay()
            event.droppedOnTarget = True
            event.deselectAll = True



    def __onLayoutBtn(self, btn):
        
	getAnalyzer().clear()
        if btn.getState() == BUTTON_DOWN:
            section = btn.getUserData()
            section.dynamicMode += 1
            mode = section.dynamicMode % 4 
            section.setTileMode(mode)
	    #print("Section, layout button, tiled: ", section, section.tiled)
        
            
    def __onNewApp(self, event):
        if self.__tileMode == TILE_MODE:
            event.app.saveBoundsBeforeTile()
            self.tileWindows(saveAllBounds=False)
        elif self.__tileMode == ONE_PER_DISPLAY_MODE:
            event.app.saveBoundsBeforeTile()
            self.tileOnePerDisplay(saveAllBounds=False)


    def __onAppKilled(self, event):
        #print("onAppKilled", event.app.appId)
        # If app is shared and in master mode
        if event.app.isShared and getMasterSiteMode():
            local_apps = getSageGate().getAppStatus()
            local_params = local_apps[str(event.app.appId)][1].split()
            directory, local_filename = os.path.split( local_params[-1] )
            #print ("Trying to kill>", local_filename, event.app.appId, event.app.sailID, event.app.appName, event.app.title, event.app.windowId, event.app.launcherId, event.app.zValue, event.app.configNum)
            for ip in event.app.sharedHosts:
                rgate, rdata = getGate(ip)
                remote_apps  = rgate.getAppStatus()
                running_apps = rdata.getRunningApps()
                #print("Remote apps:", remote_apps)
                winner = None
                for k in remote_apps:
                    ra = remote_apps[k]
                    rparams = ra[1].split()
                    rdir, rfile = os.path.split( rparams[-1] )
                    if rfile == local_filename:
                        winner = int(k)
                #print("Running apps:", running_apps)
                for v in running_apps.values():
                    remote_filename = v.title
                    #print("One remote app", v.sailID, v.appName, v.title, v.windowId, v.appId, v.launcherId, v.zValue, v.configNum)
                    if v.appName == event.app.appName:
                        if v.appId == winner:
                            #print ("    Remote app to kill>", v.sailID)
                            rgate.sendAppEvent(APP_QUIT, v.sailID)
        if event.app.section and event.app.section.tiled : 
                if event.app in event.app.section.__tiledWindows:
                        event.app.section.__tiledWindows.remove(event.app)


    #######-----------   ORGANIZATION METHODS  -------------########

    
    def unTileWindows(self, windows=None, sectionToTile=None):
        if not windows:
            windows = sectionToTile.__tiledWindows

        # if we are already in the tile mode, restore the previous state of all apps
        for app in windows:
            app.toFreeLayout()
            app.maximized = False
            sectionToTile.__tiledWindows.remove(app)

    def restoreFromMaximize(self, windowsToTile):
        # restore back to tile mode from maximized
        for w in windowsToTile:
            writeLog(LOG_WINDOW_RESTORE, w.getId(), w.getBounds())
            w.restore()
        
                            
    def moveToLeft(self, app, sectionToTile=None):
        # what is my order? 
        wId = app.getId()
        idx = 0 
        for appId in sectionToTile.windowsById : 
            if wId == appId :  
                break
            idx += 1
        x = idx % sectionToTile.numCols
        if x > 0 : 
            sectionToTile.windowsById.remove(wId)
            sectionToTile.windowsById.insert(idx-1, wId)
            self._updateTiles(sectionToTile)

    def moveToRight(self, app, sectionToTile=None):
        # what is my order? 
        wId = app.getId()
        idx = 0 
        for appId in sectionToTile.windowsById : 
            if wId == appId :  
                break
            idx += 1
        x = idx % sectionToTile.numCols

        if x < (sectionToTile.numCols-1) : 
            sectionToTile.windowsById.remove(wId)
            sectionToTile.windowsById.insert(idx+1, wId)
            self._updateTiles(sectionToTile)

    def moveToDown(self, app, sectionToTile=None):
        # what is my order? 
        wId = app.getId()
        idx = 0 
        for appId in sectionToTile.windowsById : 
            if wId == appId :  
                break
            idx += 1
        y = int(idx / sectionToTile.numCols)
        x = int(idx % sectionToTile.numCols)

        if y < (sectionToTile.numRows-1) : 
            swap_idx = int((y+1)*sectionToTile.numCols + x)
            max = len(sectionToTile.windowsById) 
            if swap_idx < max :  
                swap_wId = sectionToTile.windowsById[swap_idx]
                sectionToTile.windowsById.remove(swap_wId)
                sectionToTile.windowsById.remove(wId)
                sectionToTile.windowsById.insert(idx, swap_wId)
                sectionToTile.windowsById.insert(swap_idx, wId)
                self._updateTiles(sectionToTile)

    def moveToUp(self, app, sectionToTile=None):
        # what is my order? 
        wId = app.getId()
        idx = 0 
        for appId in sectionToTile.windowsById : 
            if wId == appId :  
                break
            idx += 1
        y = int(idx / sectionToTile.numCols)
        x = int(idx % sectionToTile.numCols)

        if y > 0 : 
            swap_idx = int((y-1)*sectionToTile.numCols + x)
            swap_wId = sectionToTile.windowsById[swap_idx]
            sectionToTile.windowsById.remove(swap_wId)
            sectionToTile.windowsById.remove(wId)
            sectionToTile.windowsById.insert(swap_idx, wId)
            sectionToTile.windowsById.insert(idx, swap_wId)
            self._updateTiles(sectionToTile)

    def updateOrder(self, app, x, y, sectionToTile=None):
        # delete 
        wId = app.getId()
        #print "windows: (", wId, ") :", sectionToTile.windowsById
        sectionToTile.windowsById.remove(wId)

        # find shortest distance  
        idx = -1 
        min_idx = 0 
        min_dis = -1 
        test_id = -1
        for w in sectionToTile.__tiledWindows: 
            # compare 
            idx += 1 
            if w.getId() == wId :
                continue
            dis = (x - w.left)*(x - w.left) + (y - w.bottom)*(y - w.bottom) 
            #print w.getId(), w.left, w.bottom, " -**- ", "dis=", dis, "min_dis=",  min_dis
            if min_dis == -1  or dis < min_dis : 
                min_dis = dis 
                min_idx = idx
                test_id = w.getId() 
        #print "min_idx=", min_idx, " id=", test_id
        sectionToTile.windowsById.insert(min_idx, wId)
        #print "windows: (", wId, ") :", sectionToTile.windowsById 

        self._updateTiles(sectionToTile)


    def _updateTiles(self, sectionToTile):
        sectionToTile.__tiledWindows = []
        r = sectionToTile.numRows-1
        c = 0
        for appId in sectionToTile.windowsById:
            if appId in sectionToTile.appWindows.keys() :
            	app = sectionToTile.appWindows[appId]
                #app.clearFixed()
            	app.fitInTile(c*sectionToTile.tileW+sectionToTile.areaX, r*sectionToTile.tileH+sectionToTile.areaY, sectionToTile.tileW, sectionToTile.tileH, margin=10, saveBounds=True)
            	sectionToTile.__tiledWindows.append(app)
            	c += 1
            	if c == sectionToTile.numCols:
                	c = 0
                	r -= 1


    def tileWindows(self, windowsToTile=None, saveAllBounds=True, sectionToTile=None):
        # save the bounds of all the windows to be tiled
        sectionToTile.appWindows = {}
        if not windowsToTile:
            windowsToTile = self.sageData.getRunningApps().values()
            for w in windowsToTile:
                if sectionToTile and w.section == sectionToTile:
                    sectionToTile.appWindows[w.getId()] = w
                    w.maximized = False
                    if saveAllBounds:
                        w.saveBoundsBeforeMax()  #w.saveBoundsBeforeTile()
            #self.__tileMode = TILE_MODE
                    
        else:      # this is only if we are "maximizing" selected apps while in tile mode already
            for w in windowsToTile:
                w.maximized = True
                if saveAllBounds:
                    w.saveBoundsBeforeMax()
                sectionToTile.appWindows[w.getId()] = w

        numWindows = len(sectionToTile.appWindows)
        if numWindows == 0: return
        

        # the tiling...
        arDiff = self.__displayAr(sectionToTile) / self.__averageWindowAr(sectionToTile.appWindows)

        # 3 scenarios... windows are on average the same aspect ratio as the display
        if arDiff >= 0.7 and arDiff <= 1.3:
            sectionToTile.numCols = int(ceil(sqrt( numWindows )))
            sectionToTile.numRows = int(ceil(numWindows / float(sectionToTile.numCols)))

        elif arDiff < 0.7: # windows are much wider than display
            c = int(round(1 / (arDiff/2)))
            if numWindows <= c:
                sectionToTile.numRows = numWindows
                sectionToTile.numCols = 1
            else:
                sectionToTile.numCols = max(2, round(numWindows / float(c)))
                sectionToTile.numRows = int(ceil(numWindows / float(sectionToTile.numCols)))

        else:              # windows are much taller than display
            c = int(round(arDiff*2))
            if numWindows <= c:
                sectionToTile.numCols = numWindows
                sectionToTile.numRows = 1
            else:
                sectionToTile.numRows = max(2, round(numWindows / float(c)))
                sectionToTile.numCols = int(ceil(numWindows / float(sectionToTile.numRows)))


        # determine the bounds of the tiling area
        if sectionToTile:
            sectionToTile.areaX = sectionToTile.getX()
            sectionToTile.areaY = sectionToTile.getY()
            sectionToTile.areaW = sectionToTile.getWidth()
            sectionToTile.areaH = sectionToTile.getHeight()
        else:
            sectionToTile.areaX = self.disp.usableZeroX
            sectionToTile.areaY = self.disp.usableZeroY
            sectionToTile.areaW = self.disp.usableW
            sectionToTile.areaH = self.disp.usableH

        sectionToTile.tileW = int(floor(sectionToTile.areaW / float(sectionToTile.numCols)))
        sectionToTile.tileH = int(floor(sectionToTile.areaH / float(sectionToTile.numRows)))

        # go through them in sorted order
        # check windows id... 
        # hyejung
        sizeExist = len(sectionToTile.windowsById)
        sizeCurrent = len(sectionToTile.appWindows)
        #print "tiling... ", sizeExist, sizeCurrent, sectionToTile.windowsById
        # remove... 
        removeList = []
        for appId in sectionToTile.windowsById:
            if not (appId in sectionToTile.appWindows) : 
                removeList.append(appId)
        #print "delete list= > ", removeList
        for appId in removeList:
            sectionToTile.windowsById.remove(appId)
        del removeList
        # add... 
        for w in sectionToTile.appWindows.keys():
            if (w in sectionToTile.windowsById) == False : 
                sectionToTile.windowsById.append(w)
        #print "tiling... -> final: ", sectionToTile.windowsById

        self._updateTiles(sectionToTile)


    def closeBtwTile(self, sectionToTile):
        # check max 
        maxWidth = 0
        rows = []
        maxHeight = 0
        totalHeight = 0
        idx = 0
        for appId in sectionToTile.windowsById:
            if appId in sectionToTile.appWindows.keys() :
            	app = sectionToTile.appWindows[appId]
                width = app.getWidth()
                if maxWidth < width : 
                    maxWidth = width 
                height = app.getHeight()
                if maxHeight < height : 
                    maxHeight = height 
            idx += 1 
            if idx == sectionToTile.numCols: 
                totalHeight += maxHeight 
                rows.append(maxHeight)
                idx = 0
                maxHeight = 0
        numRows = len(rows)
        if sectionToTile.numRows != numRows : 
            totalHeight += maxHeight 
            rows.append(maxHeight)
        #print "maxWidth = ", width, " tileW=", sectionToTile.tileW
        #print "maxHeight = ", rows, " tileH=", sectionToTile.tileH

        areaX = sectionToTile.areaX + int((sectionToTile.areaW - (maxWidth * sectionToTile.numCols))/2.0)
        areaY = sectionToTile.areaY + int((sectionToTile.areaH - totalHeight)/2.0) 
        #print "areaX = ", areaX, " areaY=", areaY

        r = sectionToTile.numRows-1
        accumHeight = totalHeight
        c = 0
        idx = 0
        if len(rows) > 0 : 
            accumHeight -= rows[0]  
            maxHeight = rows[idx]
        for app in sectionToTile.__tiledWindows:
            app.fitInTile(c*maxWidth+areaX, accumHeight+areaY, maxWidth, maxHeight, margin=0, saveBounds=True)
            c += 1
            if c == sectionToTile.numCols:
               	c = 0
               	r -= 1
                idx += 1 
                if r >= 0 :
                    accumHeight -= rows[idx]
                    maxHeight = rows[idx]


    def tileOnePerDisplay(self, windowsToTile=None, saveAllBounds=True, sectionToTile=None):
        # save the bounds of all the windows to be tiled
        sectionToTile.appWindows = {}
        if not windowsToTile:
            windowsToTile = self.sageData.getRunningApps().values()
            for w in windowsToTile:
                if sectionToTile and w.section == sectionToTile:
                    sectionToTile.appWindows[w.getId()] = w
                    w.maximized = False
                    if saveAllBounds:
                        w.saveBoundsBeforeMax()  #w.saveBoundsBeforeTile()
                    
        else:      # this is only if we are "maximizing" selected apps while in tile mode already
            for w in windowsToTile:
                w.maximized = True
                if saveAllBounds:
                    w.saveBoundsBeforeMax()
                sectionToTile.appWindows[w.getId()] = w

        numWindows = len(sectionToTile.appWindows)
        if numWindows == 0: return
        

        # the tiling...
        # tile
        # determine the bounds of the tiling area

        sectionToTile.areaX = sectionToTile.getX()
        sectionToTile.areaY = sectionToTile.getY()
        sectionToTile.areaW = sectionToTile.getWidth()
        sectionToTile.areaH = sectionToTile.getHeight()

        a_dispW = int(floor(self.sageW / float(self.disp.cols)))
        a_dispH = int(floor(self.sageH / float(self.disp.rows)))
        sectionToTile.numCols = int(sectionToTile.areaW / a_dispW) 
        sectionToTile.numRows = int(sectionToTile.areaH / a_dispH) + 1
        #print "row=", sectionToTile.numRows, "column=", sectionToTile.numCols 
        #print "x=", sectionToTile.areaX, "y=", sectionToTile.areaY
        #print "width=", sectionToTile.areaW, "height=", sectionToTile.areaH

        #m = int(self.disp.getMullionSize() / 2.0)
        sectionToTile.tileW = int(floor(self.sageW / float(self.disp.cols)))
        #sectionToTile.tileH = int(floor(self.sageH / float(self.disp.rows)))
        #sectionToTile.tileH = int(floor((self.disp.usableH -self.disp.usableZeroY) / float(self.disp.rows)))
        sectionToTile.tileH = int(floor((self.sageH -self.disp.usableZeroY) / float(self.disp.rows)))

        # go through them in sorted order
        # check windows id... 
        # hyejung
        sizeExist = len(sectionToTile.windowsById)
        sizeCurrent = len(sectionToTile.appWindows)
        #print "tiling... ", sizeExist, sizeCurrent, sectionToTile.windowsById
        # remove... 
        removeList = []
        for appId in sectionToTile.windowsById:
            if not (appId in sectionToTile.appWindows) : 
                removeList.append(appId)
        #print "delete list= > ", removeList
        for appId in removeList:
            sectionToTile.windowsById.remove(appId)
        del removeList
        # add... 
        for w in sectionToTile.appWindows.keys():
            if (w in sectionToTile.windowsById) == False : 
                sectionToTile.windowsById.append(w)
        #print "tiling... -> final: ", sectionToTile.windowsById

        self._updateTiles(sectionToTile)



    #######-----------   HELPER METHODS  -------------########

 

    def __averageWindowAr(self, appWindows):
        num = len(appWindows)
        if num == 0: return 1.0

        totAr = 0.0
        for appId, app in appWindows.items():
            totAr += app.getAspectRatio()

        return totAr / num


    def __displayAr(self, section=None):
        if section:
            return section.getWidth() / float(section.getHeight())
        else:
            return self.disp.usableW / float(self.disp.usableH)
